#ifndef SIMPLE_SUPPORT_META_BIND_HPP
#define SIMPLE_SUPPORT_META_BIND_HPP

#include <cstddef> // std::nullptr_t
#include <type_traits> // std::false_type std::true_type std::enable_if_t
#include <utility> // std::make_index_sequence

#include "list.hpp" // list list_size_v collect_list_t
#include "integer_sequence.hpp" // make_index_range
#include "pend.hpp" // concat_t
#include "misc.hpp" // nop

namespace simple::support::meta
{

	template <typename... Bindings>
	struct first_binding {};
	template <typename First, typename... Rest>
	struct first_binding<First, Rest...>
	{
		using binding = First;
	};

	template <template <typename...> typename F,
		typename... Bindings>
	struct bind : first_binding<Bindings...>
	{
		template <typename... Ts> using function = F<Ts...>;

		template <typename... Rest>
		using bound = F<Bindings..., Rest...>;

		using bindings = list<Bindings...>;
	};

	template <template <typename...> typename F,
		typename... Bindings>
	struct bind_after : first_binding<Bindings...>
	{
		template <typename... Ts> using function = F<Ts...>;

		template <typename... Rest>
		using bound = F<Rest..., Bindings...>;

		using bindings = list<Bindings...>;
	};

	// TODO: would prefer a type constrained type alias bound_t in bind struct above, along with constant bound_v
	template <template <typename...> typename F,
		typename... Bindings>
	struct bind_t
	{
		template <typename... Rest>
		using bound = typename bind<F,Bindings...>::template bound<Rest...>::type;
	};

	template <template <typename...> typename F,
		typename... Bindings>
	struct bind_after_t
	{
		template <typename... Rest>
		using bound = typename bind_after<F,Bindings...>::template bound<Rest...>::type;
	};

	template <template <typename...> typename F, typename Bindings>
	struct bind_list;
	template <template <typename...> typename F,
		template <typename...> class List,
		typename... Bindings>
	struct bind_list<F, List<Bindings...>>
	{
		using type = bind<F, Bindings...>;
	};
	template <template <typename...> typename F, typename Bindings>
	using bind_list_t = typename bind_list<F, Bindings>::type;

	template <template <typename...> typename F, typename Bindings>
	struct bind_list_after;
	template <template <typename...> typename F,
		template <typename...> class List,
		typename... Bindings>
	struct bind_list_after<F, List<Bindings...>>
	{
		using type = bind_after<F, Bindings...>;
	};
	template <template <typename...> typename F, typename Bindings>
	using bind_list_after_t = typename bind_list_after<F, Bindings>::type;

	template <template <template <typename...> class, typename...> typename Binder,
		template <typename...> typename F,
		typename Bindings>
	struct bind_list_with;
	template <template <template <typename...> class, typename...> typename Binder,
		template <typename...> typename F,
		template <typename...> class List,
		typename... Bindings>
	struct bind_list_with<Binder, F, List<Bindings...>>
	{
		using type = Binder<F, Bindings...>;
	};
	template <template <template <typename...> class, typename...> typename Binder,
		template <typename...> typename F,
		typename Bindings>
	using bind_list_with_t = typename bind_list_with<Binder,F,Bindings>::type;

	template <typename T, typename Enable = std::nullptr_t>
	struct has_bindings : std::false_type {};
	template <typename T>
	struct has_bindings<T,
		decltype(nop<typename T::bindings>{}, nullptr)>
		: std::true_type {};
	template <typename T>
	constexpr auto has_bindings_v = has_bindings<T>::value;

	template <typename T, typename Enable = std::nullptr_t>
	struct has_binding : std::false_type {};
	template <typename T>
	struct has_binding<T,
		decltype(nop<typename T::binding>{}, nullptr)>
		: std::true_type {};
	template <typename T>
	constexpr auto has_binding_v = has_binding<T>::value;

	template <typename T, typename Enabled = void>
	struct get_bindings;
	template <typename T>
	struct get_bindings<T,
		std::enable_if_t<has_bindings_v<T>>>
	{
		using type = typename T::bindings;
	};
	template <typename T>
	struct get_bindings<T,
		std::enable_if_t<has_binding_v<T>>>
	{
		using type = list<typename T::binding>;
	};

	template <typename T>
	struct get_binder;
	template <template <template <typename...> class, typename...> typename Binder,
		template <typename...> class Function, typename... Bindings>
	struct get_binder<Binder<Function, Bindings...>>
	{
		template <template <typename...> class F, typename... Bs>
		using function = Binder<F, Bs...>;
	};

	template <typename Over, typename Under,
		auto begin = list_size_v<Over>,
		auto end = list_size_v<Under>,
		typename Tail = make_index_range<begin, end>
	>
	using overlap_t = concat_t<Over, collect_list_t<Under, Tail>>;

	template <typename Functor, typename Result,
		typename Enabled = void>
	struct rebind {};
	template <typename Functor, typename Result>
	struct rebind
	<
		Functor, Result,
		std::enable_if_t<has_bindings_v<Result> || has_binding_v<Result>>
	>
	{
		using type = bind_list_with_t
		<
			get_binder<Functor>::template function,
			Functor::template function,
			overlap_t
			<
				typename get_bindings<Result>::type,
				typename Functor::bindings
			>
		>;
	};
	template <typename Functor, typename Result>
	struct rebind
	<
		Functor, Result,
		std::enable_if_t<
			not (has_bindings_v<Result> || has_binding_v<Result>) >
	>
	{
		using type = Functor;
	};
	template <typename Functor, typename Result>
	using rebind_t = typename rebind<Functor, Result>::type;



} // namespace simple::support::meta

#endif /* end of include guard */
